package com.tsfyun.scm.service.impl.order;

import cn.hutool.core.collection.CollUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.tsfyun.common.base.dto.TransactionFlowDTO;
import com.tsfyun.common.base.enums.DistributedLockEnum;
import com.tsfyun.common.base.enums.finance.TransactionCategoryEnum;
import com.tsfyun.common.base.enums.finance.TransactionTypeEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.util.StringUtils;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.scm.dto.order.CostChangeRecordDTO;
import com.tsfyun.scm.dto.order.CostChangeRecordQTO;
import com.tsfyun.scm.entity.customer.ImpQuote;
import com.tsfyun.scm.entity.order.CostChangeRecord;
import com.tsfyun.scm.entity.order.ImpOrder;
import com.tsfyun.scm.entity.order.ImpOrderCost;
import com.tsfyun.scm.entity.system.ExpenseSubject;
import com.tsfyun.scm.mapper.order.CostChangeRecordMapper;
import com.tsfyun.scm.service.customer.IImpQuoteService;
import com.tsfyun.scm.service.finance.ITransactionFlowService;
import com.tsfyun.scm.service.finance.IWriteOffService;
import com.tsfyun.scm.service.order.ICostChangeRecordService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.order.IImpOrderCostService;
import com.tsfyun.scm.service.order.IImpOrderInvoiceService;
import com.tsfyun.scm.service.order.IImpOrderService;
import com.tsfyun.scm.service.system.IExpenseSubjectService;
import com.tsfyun.scm.util.AccountUtils;
import com.tsfyun.scm.util.ExpenseSubjectUtil;
import com.tsfyun.scm.vo.order.CostChangeRecordVO;
import com.tsfyun.scm.vo.order.ImpOrderInvoiceVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * <p>
 * 费用变更记录 服务实现类
 * </p>
 *

 * @since 2020-05-27
 */
@RefreshScope
@Slf4j
@Service
public class CostChangeRecordServiceImpl extends ServiceImpl<CostChangeRecord> implements ICostChangeRecordService {

    @Autowired
    private CostChangeRecordMapper costChangeRecordMapper;
    @Autowired
    private IImpOrderService impOrderService;
    @Autowired
    private IExpenseSubjectService expenseSubjectService;
    @Autowired
    private IImpOrderCostService impOrderCostService;
    @Autowired
    private IImpQuoteService impQuoteService;
    @Autowired
    private ITransactionFlowService transactionFlowService;
    @Autowired
    private RedisLockRegistry redisLockRegistry;
    @Value("${redis.lock.time:5}")
    private Integer lockTime;
    @Autowired
    private IImpOrderInvoiceService impOrderInvoiceService;
    @Autowired
    private IWriteOffService writeOffService;

    @Override
    public PageInfo<CostChangeRecordVO> pageList(CostChangeRecordQTO qto) {
        Map<String,Object> params = beanMapper.map(qto, Map.class);
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        List<CostChangeRecordVO> list = costChangeRecordMapper.list(params);
        return new PageInfo<>(list);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void adjust(CostChangeRecordDTO dto) {
        ExpenseSubject expenseSubject = expenseSubjectService.getById(dto.getExpenseSubjectId());
        Optional.ofNullable(expenseSubject).orElseThrow(()->new ServiceException("费用科目错误"));
        //判断哪些科目不允许新增或修改
        TsfPreconditions.checkArgument(!Lists.newArrayList("A0002","A0003","A0004","A0005").contains(dto.getExpenseSubjectId()),new ServiceException(String.format("费用科目【%s】不允许调整费用",expenseSubject.getName())));
        String lockKey = DistributedLockEnum.ORDER_COST_ADJUST.getCode() + ":" + dto.getImpOrderNo();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("未获取到锁，订单号：【%s】", dto.getImpOrderNo()));
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            ImpOrder impOrder = impOrderService.findByDocNo(dto.getImpOrderNo());
            Optional.ofNullable(impOrder).orElseThrow(()->new ServiceException("订单号错误"));
            //获取订单开票情况
            List<ImpOrderInvoiceVO> impOrderInvoiceVOS = impOrderInvoiceService.idsList(Arrays.asList(impOrder.getId().toString()));
            TsfPreconditions.checkArgument(CollUtil.isNotEmpty(impOrderInvoiceVOS),new ServiceException("订单不存在"));
            ImpOrderInvoiceVO orderInvoiceVO = impOrderInvoiceVOS.get(0);

            TsfPreconditions.checkArgument(Objects.isNull(orderInvoiceVO.getScId()),new ServiceException("订单已经生成销售合同，不允许调整费用"));
            TsfPreconditions.checkArgument(Objects.isNull(orderInvoiceVO.getInvId()),new ServiceException("订单已经生成发票申请，不允许调整费用"));

            log.info("订单【{}】【{}】费用，费用科目【{}】，费用金额【{}】",impOrder.getDocNo(),Objects.nonNull(dto.getImpOrderCostId()) ? "修改" : "新增",expenseSubject.getName(),dto.getCostAmount());
            //根据订单获取报价
            ImpQuote impQuote = impQuoteService.getById(impOrder.getImpQuoteId());
            BigDecimal originCostAmount = BigDecimal.ZERO;
            ImpOrderCost impOrderCost;
            if(Objects.nonNull(dto.getImpOrderCostId())) {//修改费用
                impOrderCost = impOrderCostService.getById(dto.getImpOrderCostId());
                Optional.ofNullable(impOrderCost).orElseThrow(()->new ServiceException("费用信息不存在"));
                //修改订单费用不允许修改费用科目
                TsfPreconditions.checkArgument(Objects.equals(impOrderCost.getExpenseSubjectId(),dto.getExpenseSubjectId()),new ServiceException("修改订单费用不允许修改费用科目"));
                //校验订单费用信息与订单是否匹配
                TsfPreconditions.checkArgument(Objects.equals(impOrderCost.getImpOrderId().toString(),impOrder.getId().toString()),new ServiceException("订单费用与订单不匹配"));
                //检查是否调整了费用（前端也需要做验证）
                TsfPreconditions.checkArgument(impOrderCost.getReceAmount().compareTo(dto.getCostAmount()) != 0,new ServiceException("费用没有变更"));
                TsfPreconditions.checkArgument(Objects.equals(impOrderCost.getIsAllowEdit(),Boolean.TRUE),new ServiceException("该费用不允许修改"));
                //获取订单修改前费用
                originCostAmount = impOrderCost.getReceAmount();
                impOrderCost.setActualAmount(originCostAmount);
                //应收金额
                impOrderCost.setReceAmount(dto.getCostAmount());
                impOrderCost.setMemo(dto.getMemo());
                //防止系统重复计算
                impOrderCost.setIsAutomatic(Boolean.FALSE);
                impOrderCost.setOperator(SecurityUtil.getCurrentPersonName());
                //计算账期
                impOrderCost.setHappenDate(impOrder.getOrderDate());
                impOrderCost.setPeriodDate(AccountUtils.agencyFeesPeriod(impOrderCost.getHappenDate(),impQuote));
                impOrderCost.setLateFeeDate((impQuote.getGraceDay()>0) ? impOrderCost.getPeriodDate().plusDays(impQuote.getGraceDay()) : impOrderCost.getPeriodDate());
                impOrderCost.setOverdueRate(impQuote.getOverdueRate());
                impOrderCostService.updateById(impOrderCost);
                //检查订单费用是否已经核销
                if(impOrderCost.getAcceAmount().compareTo(BigDecimal.ZERO) == 1){
                    //取消核销
                    writeOffService.cancelWriteOffByImpOrderCost(impOrderCost);
                }
            } else {//新增费用
                //需判断订单是否存在该科目的费用
                List<ImpOrderCost> existsImpOrderCost = impOrderCostService.getByExpenseSubject(impOrder.getId(),expenseSubject.getId());
                TsfPreconditions.checkArgument(CollUtil.isEmpty(existsImpOrderCost),new ServiceException(String.format("订单已经存在科目为【%s】的费用信息，您可以直接修改。",expenseSubject.getName())));
                impOrderCost = new ImpOrderCost();
                impOrderCost.setExpenseSubjectId(expenseSubject.getId());
                impOrderCost.setImpOrderId(impOrder.getId());

                impOrderCost.setCostClassify(ExpenseSubjectUtil.getCostClassify(expenseSubject.getId()));
                impOrderCost.setIsAutomatic(Boolean.FALSE);
                impOrderCost.setIsFirstWriteOff(Boolean.FALSE);
                impOrderCost.setIsLock(impOrder.getIsImp());
                //计算账期
                impOrderCost.setHappenDate(impOrder.getOrderDate());
                impOrderCost.setPeriodDate(AccountUtils.agencyFeesPeriod(impOrderCost.getHappenDate(),impQuote));
                impOrderCost.setLateFeeDate((impQuote.getGraceDay()>0) ? impOrderCost.getPeriodDate().plusDays(impQuote.getGraceDay()) : impOrderCost.getPeriodDate());
                impOrderCost.setOverdueRate(impQuote.getOverdueRate());
                impOrderCost.setMark(impOrder.getDocNo());
                impOrderCost.setOperator(SecurityUtil.getCurrentPersonName());
                impOrderCost.setIsAllowEdit(Boolean.TRUE);//人工新增或修改的可以修改
                //原始费用
                impOrderCost.setActualAmount(BigDecimal.ZERO);
                //应收金额
                impOrderCost.setReceAmount(dto.getCostAmount());
                impOrderCost.setMemo(dto.getMemo());
                impOrderCostService.saveNonNull(impOrderCost);
            }
            //登记费用调整记录
            CostChangeRecord costChangeRecord = new CostChangeRecord();
            costChangeRecord.setImpOrderId(impOrder.getId());
            costChangeRecord.setImpOrderNo(impOrder.getDocNo());
            costChangeRecord.setCustomerId(impOrder.getCustomerId());
            costChangeRecord.setExpenseSubjectId(expenseSubject.getId());
            costChangeRecord.setExpenseSubjectName(expenseSubject.getName());
            costChangeRecord.setOriginCostAmount(originCostAmount);
            costChangeRecord.setCostAmount(impOrderCost.getReceAmount());
            costChangeRecord.setMemo(impOrderCost.getMemo());
            super.saveNonNull(costChangeRecord);

            //处理交易流水
            if(Objects.equals(Boolean.TRUE,impOrderCost.getIsLock())) {
                log.info("订单【{}】已经处于确定进口，登记交易流水",impOrder.getDocNo());
                TransactionFlowDTO param = new TransactionFlowDTO();
                param.setCustomerId(impOrder.getCustomerId());
                param.setTransactionType(TransactionTypeEnum.IMP_ORDER.getCode());
                param.setTransactionNo(impOrder.getDocNo());
                param.setMemo(String.format("费用调整:%s[%s]改[%s] %s",expenseSubject.getName(),originCostAmount.toString(),impOrderCost.getReceAmount().toString(),StringUtils.removeSpecialSymbol(impOrderCost.getMemo())));
                BigDecimal amount = impOrderCost.getReceAmount().subtract(originCostAmount).setScale(2,BigDecimal.ROUND_HALF_UP);
                param.setAmount(amount.abs());
                if(amount.compareTo(BigDecimal.ZERO)>=0){
                    param.setTransactionCategory(TransactionCategoryEnum.OUTCOME.getCode());
                }else{
                    param.setTransactionCategory(TransactionCategoryEnum.INCOME.getCode());
                }
                transactionFlowService.recordTransactionFlow(param);
                //执行订单费用核销
                writeOffService.writeOffOrderId(impOrder.getId());
            }
        } catch (InterruptedException e) {
            log.error("费用调整获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeByOrderId(Long orderId) {
        costChangeRecordMapper.removeByOrderId(orderId);
    }
}
